package cn.xo68.boot.web.web;

import cn.xo68.core.util.JsonUtil;
import cn.xo68.core.util.StringTools;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 *  web context : web上下文相关处理
 * @author wuxie
 * @date 2018-8-15
 */
public class WebContext {

    private final ApplicationContext applicationContext;

    private final UrlPathHelper urlPathHelper;

    private final JsonUtil jsonUtil;

    private static ConcurrentMap<String,HandlerMethod> handlerMethodConcurrentMap=new ConcurrentHashMap<>();

    public WebContext(ApplicationContext applicationContext,JsonUtil jsonUtil) {
        this.applicationContext = applicationContext;
        this.urlPathHelper=new UrlPathHelper();
        this.jsonUtil=jsonUtil;
    }

    /**
     * 生成跨域支持
     * @param allowOrigins
     * @return
     */
    public Map<String, String> generateCorsHeader(String allowOrigins, boolean outputAllowOrigin){
        Map<String, String> headers=new HashMap<>();
        if(StringTools.isNotEmpty(allowOrigins)){
            if(outputAllowOrigin){
                headers.put("Access-Control-Allow-Origin", allowOrigins);
            }
            headers.put("Access-Control-Allow-Headers", "Content-Type,Accept,Host,x-access-token, Access-Control-Allow-Headers, Access-Control-Request-Method, Authorization, X-Requested-With");
            headers.put("Access-Control-Allow-Methods", "*");
            headers.put("Access-Control-Max-Age", "3600");
        }
        return headers;
    }

    /**
     * 构建认证服务端响应头
     * @param otherHeaders
     * @return
     */
    public HttpHeaders buildHeaders( boolean outputAllowOrigin, Map<String,String> otherHeaders){
        return buildHeaders(false,"*",outputAllowOrigin,otherHeaders);
    }

    /**
     * 构建认证服务端响应头
     * @param cahce
     * @param allowOrigins
     * @param otherHeaders
     * @return
     */
    public HttpHeaders buildHeaders(boolean cahce,String allowOrigins, boolean outputAllowOrigin, Map<String,String> otherHeaders){
        HttpHeaders headers = new HttpHeaders();
        //cors
        if(StringTools.isNotEmpty(allowOrigins)){
            Map<String, String> corsHeader=generateCorsHeader(allowOrigins, outputAllowOrigin);
            for(Map.Entry<String, String> map: corsHeader.entrySet()){
                headers.add(map.getKey(),map.getValue());
            }
        }

        //禁止缓存
        if(cahce){
            headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
            headers.add("Pragma", "no-cache");
        }


        if(otherHeaders!=null){
            for(Map.Entry<String, String> map: otherHeaders.entrySet()){
                headers.add(map.getKey(),map.getValue());
            }
        }

        return headers;
    }

    /**
     * 输出响应
     * @param response
     * @param responseEntity
     * @param <T>
     */
    public <T> void writeJsonResponse(HttpServletResponse response, ResponseEntity<T> responseEntity){

        response.setStatus(responseEntity.getStatusCodeValue());
        //write header
        HttpHeaders headers = responseEntity.getHeaders();
        for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
            String headerName = entry.getKey();
            for (String headerValue : entry.getValue()) {
                response.addHeader(headerName, headerValue);
            }
        }
        // HttpServletResponse exposes some headers as properties: we should include those if not already present
        if (response.getContentType() == null && headers.getContentType() != null) {
            response.setContentType(headers.getContentType().toString());
        }
        if (response.getCharacterEncoding() == null && headers.getContentType() != null &&
                headers.getContentType().getCharset() != null) {
            response.setCharacterEncoding(headers.getContentType().getCharset().name());
        }

        MediaType meidaType = headers.getContentType();
        try(ServletOutputStream outputStream=response.getOutputStream()){
            T body = responseEntity.getBody();
            if(body!=null){
                outputStream.print(jsonUtil.toString(body));
            }
            outputStream.flush();
        }catch (IOException e){
            throw new IllegalStateException("Write JsonResponse exception", e);
        }
    }

    /**
     * 得到请求匹配的控制器方法
     * @param httpServletRequest
     * @return
     */
    public HandlerMethod getMatchingHandlerMethod(HttpServletRequest httpServletRequest){


        String lookupPath=this.urlPathHelper.getLookupPathForRequest(httpServletRequest);
        String stringRequestMethod= httpServletRequest.getMethod().toUpperCase();
        String cacheName= stringRequestMethod+"@"+lookupPath;
        if(handlerMethodConcurrentMap.containsKey(cacheName)){
            return handlerMethodConcurrentMap.get(cacheName);
        }

        RequestMethod requestMethod=RequestMethod.valueOf(stringRequestMethod);

        RequestMappingHandlerMapping requestMappingHandlerMapping= applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> handleMethos = requestMappingHandlerMapping.getHandlerMethods();

        HandlerMethod handlerMethod=null;
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handleMethos.entrySet()){
            RequestMappingInfo requestMappingInfo=entry.getKey();
            List<String> patterns=requestMappingInfo.getPatternsCondition().getMatchingPatterns(lookupPath);
            Set<RequestMethod> methods = requestMappingInfo.getMethodsCondition().getMethods();

            if(patterns!=null && !patterns.isEmpty() && (methods.isEmpty() || methods.contains(requestMethod))){
               handlerMethod=entry.getValue();
                handlerMethodConcurrentMap.put(cacheName,handlerMethod);
                break;
            }
        }

        return handlerMethod;
    }

    /**
     * 是否返回json响应
     * @param httpServletRequest
     * @return
     */
    public  boolean isResponseJson(HttpServletRequest httpServletRequest){

        HandlerMethod handlerMethod=getMatchingHandlerMethod(httpServletRequest);
        if(handlerMethod!=null){
            boolean  isRestControoler= Arrays.stream(handlerMethod.getBeanType().getAnnotations()).anyMatch((anno)->{
                return  anno.annotationType().equals(RestController.class);
            });
            if(isRestControoler){
                return true;
            }

            boolean isResponseBody= Arrays.stream(handlerMethod.getMethod().getAnnotations()).anyMatch((anno)->{
                return  anno.annotationType().equals(ResponseBody.class);
            });
            if(isResponseBody){
                return true;
            }

            return false;
        }else{
            return false;
        }
    }

    /**
     * 获取完整请求地址
     * @param request
     * @param dispatch
     * @return
     */
    public String getRequestFullUri(HttpServletRequest request, boolean dispatch) {
        StringBuilder sbUri=new StringBuilder();
        sbUri.append(request.getScheme());
        sbUri.append("://");
        sbUri.append(request.getServerName());
        sbUri.append(":");
        sbUri.append(request.getServerPort());
        if(dispatch){
            sbUri.append(request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));
        }else {
            sbUri.append(request.getServletPath());
        }

        if(StringTools.isNotEmpty(request.getQueryString())){
            sbUri.append("?");
            sbUri.append(request.getQueryString());
        }
        return sbUri.toString();
    }
}
